//---------------------------------------------------------------------
// Author: jachymko
//
// Description: Type name cache 
//
// Creation Date: Jan 8, 2007
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

using Pscx.Collections;

namespace Pscx.Commands.TabExpansion.Caches
{
    internal sealed class TypeNameCache : TabExpansionCache
    {
        public static readonly TypeNameCache Instance = new TypeNameCache();

        private readonly TypeCacheLoader _loader;
        private readonly RootNode _cache;
        private readonly Node _system;

        private TypeNameCache()
        {
            _cache = new RootNode();
            _loader = new TypeCacheLoader(_cache);
            _system = _cache.AddNamespace("System");

            InitializeDefaultNodes();
        }

        public IEnumerable<String> Expand(string prefix)
        {
            EnsureInitialized();

            string[] parts = prefix.Split('.');

            string childPrefix = parts[parts.Length - 1];
            string resultPrefix = string.Join(".", parts, 0, parts.Length - 1);

            if (resultPrefix.Length > 0)
            {
                resultPrefix += '.';
            }

            return CombineEnumerable<String>
            (
                TryExpand(_cache, parts, childPrefix, resultPrefix),
                TryExpand(_system, parts, childPrefix, resultPrefix)
            );
        }

        internal override void OnInitialize()
        {
            AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;

            foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                LoadAssembly(asm);
            }
        }

        private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs e)
        {
            LoadAssembly(e.LoadedAssembly);
        }

        private void LoadAssembly(Assembly asm)
        {
            if (!(asm is AssemblyBuilder))
            {
                _loader.Push(asm);
            }
        }

        private void InitializeDefaultNodes()
        {
            _cache.Add("Adsi");
            _cache.Add("Int");
            _cache.Add("Long");
            _cache.Add("Bool");
            _cache.Add("Float");
            _cache.Add("Regex");
            _cache.Add("Xml");
            _cache.Add("ScriptBlock");
            _cache.Add("Switch");
            _cache.Add("Hashtable");
            _cache.Add("Ref");
            _cache.Add("PSObject");
            _cache.Add("Wmi");
            _cache.Add("WmiClass");
            _cache.Add("WmiSearcher");
        }

        private IEnumerable<String> TryExpand(Node root, string[] parts, string childPrefix, string resultPrefix)
        {
            Node n = root;

            for (int i = 0 ; i < parts.Length - 1 ; i++)
            {
                if (n == null) return null;

                n = n[parts[i]];
            }

            if (n == null) return null;

            return n.GetChildNames(childPrefix, resultPrefix);
        }

        private IEnumerable<T> CombineEnumerable<T>(params IEnumerable<T>[] enumerables)
        {
            foreach (IEnumerable<T> collection in enumerables)
            {
                if (collection == null) continue;

                foreach (T item in collection)
                {
                    yield return item;
                }
            }
        }

        private sealed class TypeCacheLoader
        {
            private readonly RootNode _cache;
            private readonly InterlockedStack<Type[]> _pending = new InterlockedStack<Type[]>();

            private int _loadingCount;

            public TypeCacheLoader(RootNode cache)
            {
                _cache = cache;
            }

            public void Push(Assembly asm)
            {
                Interlocked.Increment(ref _loadingCount);
                
                _pending.Push(asm.GetExportedTypes());

                if (Interlocked.Decrement(ref _loadingCount) <= 0)
                {
                    do
                    {
                        Type[] types = _pending.Pop();

                        if (types == null)
                        {
                            break;
                        }

                        lock (_cache.SyncRoot)
                        {
                            for (int i = 0; i < types.Length; i++)
                            {
                                _cache.Add(types[i]);
                            }
                        }
                    }
                    while (true);
                }
            }
        }

        private abstract class Node
        {
            private readonly string _name;

            private Dictionary<String, Node> _nodes;

            public Node(string name)
            {
                _name = name;
            }

            public string Name
            {
                get { return _name; }
            }

            public void Add(Type type)
            {
                Node n = this;

                if (type.Namespace != null)
                {
                    string[] ns = type.Namespace.Split('.');

                    for (int i = 0; i < ns.Length; i++)
                    {
                        n = n.EnsureNamespace(ns[i]);
                    }
                }

                n.Add(new TypeNode(type.Name));
            }

            public void Add(string name)
            {
                Add(new TypeNode(name));
            }

            public Node this[string name]
            {
                get
                {
                    if (_nodes != null && _nodes.ContainsKey(name))
                        return _nodes[name];

                    return null;
                }
            }

            public IEnumerable<String> ChildNames
            {
                get { return _nodes.Keys; }
            }

            public IEnumerable<String> GetChildNames(string childPrefix, string resultPrefix)
            {
                foreach (string n in _nodes.Keys)
                {
                    if (n.StartsWith(childPrefix, StringComparison.OrdinalIgnoreCase))
                    {
                        if (_nodes[n] is NamespaceNode)
                        {
                            yield return '[' + resultPrefix + n + '.';
                        }
                        else
                        {
                            yield return '[' + resultPrefix + n + ']';
                        }
                    }
                }
            }

            protected Node EnsureNamespace(string ns)
            {
                EnsureDictionary();

                if (!_nodes.ContainsKey(ns))
                {
                    _nodes[ns] = new NamespaceNode(ns);
                }

                return _nodes[ns];
            }

            private void Add(Node node)
            {
                EnsureDictionary();

                _nodes[node.Name] = node;
            }

            private void EnsureDictionary()
            {
                if (_nodes == null)
                {
                    _nodes = new Dictionary<string, Node>(StringComparer.OrdinalIgnoreCase);
                }
            }
        }

        private sealed class NamespaceNode : Node
        {
            public NamespaceNode(string name)
                : base(name)
            {
            }
        }

        private sealed class TypeNode : Node
        {
            public TypeNode(string name)
                : base(name)
            {
            }
        }

        private sealed class RootNode : Node
        {
            public readonly object SyncRoot = new object();
            
            public RootNode() 
                : base(null) 
            {
            }

            public Node AddNamespace(string name)
            {
                return EnsureNamespace(name);
            }
        }
    }
}
